# forward-compat boilerplate
from __future__ import absolute_import
from __future__ import with_statement
__metaclass__ = type

from fenton import data
from fenton import view
from fenton import util
from fenton import types
from fenton import getmeta
from fenton import widgets
from fenton import security


def perms(default=None, debug=False, **kw):
    return util.newdict({
        'list': default,
        'view': default,
        'edit': default,
        'clone': default,
        'create': default,
        'delete': default,
    }, kw)


def public(**kw):
    return perms(view=security.PUBLIC, **kw)


def prepare_static(cx):
    'compress css/js'
    import subprocess, os
    dirs = [os.path.join(d, 'static') for d in cx.app.source_dirs()]
    compr = cx.app.config['compressor.jar']
    outpath = cx.app.config['compress.out']

    out = 'pack-%s.js' % cx.app.static_version
    out = os.path.join(outpath, 'js', out)
    p = subprocess.Popen(['java', '-jar', compr, '--type', 'js', '-o', out],
                         stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE)
    for fn in widgets.get_all_js():
        for d in dirs:
            fp = os.path.join(d, fn)
            if not os.path.exists(fp):
                continue
            with open(fp) as f:
                for line in f:
                    line = line.replace('/*!', '/*')
                    p.stdin.write(line)
                    p.stdin.write('\n')
    p.communicate()
    p.wait()

    out = 'pack-%s.css' % cx.app.static_version
    out = os.path.join(outpath, 'css', out)
    import re
    sre = re.compile('\s+')
    with open(out, 'wb') as fout:
        for fn in widgets.get_all_css():
            for d in dirs:
                fp = os.path.join(d, fn)
                if not os.path.exists(fp):
                    continue
                f = open(fp)
                fout.write(sre.sub(' ', f.read()))
                fout.write(' ')

    return
    # for future upgrades method
    from fenton import web
    r = web.mako_renderer(cx.app)
    for fn in widgets.ALL_MAKO:
        path = '/%s.mako' % fn.replace('.', '/')
        r.get_template(path)




class External(view.Screen):
    priv = True
    bindings = {}
    has_error = False
    def __init__(self, cx, url, label=None):
        self.context = cx
        assert url
        self.url = url
        self.label = label
    def get_url(self):
        return self.url
    def get_title(self):
        return self.label
    def render(self):
        self.request.redirect(self.url)



class LoginScreen(view.FormDialog):
    login_message = None
    bind_update = None

    def build_dialog_buttons(self, panel):
        widgets.submit_button(panel,
                               type='primary',
                               label='Login',
                               id=self.bind(self.do_login))
        widgets.submit_button(panel,
                               type='secondary',
                               label='Help',
                               id=self.bind(self.do_help))
        widgets.message_button(panel, text=self.login_message)

    def get_url(self):
        path = 'system/login'
        url = self.request.app_url(path)
        if self.model.url:
            from fenton import form
            from urllib import quote_plus as q
            from urlparse import urlsplit, urlunsplit
            args = form.encode({'url': self.model.url})
            qs = '&'.join('%s=%s'%(k,q(v)) for (k, v) in args.items() if v)
            scheme, host, path, _, frag = urlsplit(url)
            url = urlunsplit((scheme, host, path, qs, frag))
        return url

    def do_login(self):
        self.update()
        if not self.has_error:
            ok = self.model.authenticate()
            if ok:
                return self.login_ok(ok)
            self.login_message = 'Failed'

    def login_ok(self, ok):
        if ok.show_landing():
            vs = getmeta(ok).view_screen
            self.replace(vs(self.context, ok, self.priv, None))
        else:
            self.replace(External(self.context, self.model.url))

    def do_help(self):
        obj = self.model.get_help()
        vs = getmeta(obj).view_screen
        self.replace(vs(self.context, obj, self.priv, self))




class LoginOkScreen(view.CustomDialog):
    screen_title = 'DEFAULT LOGIN OK'

    def build_dialog_buttons(self, panel):
        widgets.submit_button(panel,
                               type='primary',
                               label='Continue',
                               id=self.bind(self.do_continue))
        if self.model.allow_chpasswd():
            widgets.submit_button(panel,
                                   type='secondary',
                                   label='Change password',
                                   id=self.bind(self.go_chpasswd))
        if self.model.allow_switch():
            widgets.submit_button(panel,
                                   type='secondary',
                                   label='Switch user',
                                   id=self.bind(self.go_switch_user))

    def do_continue(self):
        self.replace(External(self.context, self.model.url))

    def go_chpasswd(self):
        url = '?'.join(map(self.request.absolute_url,
                           ('system/chpasswd', self.model.url)))
        self.request.redirect(url)

    def go_switch_user(self):
        obj = self.model.get_switcher()
        vs = getmeta(obj).view_screen
        self.replace(vs(self.context, obj, True, self))





class LoginHelpScreen(view.CustomDialog):
    registrable = False
    def build_dialog_buttons(self, panel):
        widgets.submit_button(panel,
                               type='primary',
                               label='Back to login',
                               id=self.bind(self.pop))
        if self.model.allow_chpasswd():
            widgets.submit_button(panel,
                                   type='secondary',
                                   label='Change password',
                                   id=self.bind(self.to_chpasswd))
        else:
            widgets.text(panel, 'Password change not available')

    def to_chpasswd(self):
        abs = self.request.absolute_url
        url = abs('system/chpasswd') + '?' + abs(self.model.url)
        self.replace(External(self.context, url))




class LogoutScreen(view.Dialog):
    screen_title = 'DEFAULT LOGOUT'

    def build_dialog(self, sheet):
        widgets.screen_widget(sheet, template_file=self.template_file)

    def build_dialog_buttons(self, panel):
        widgets.submit_button(panel,
                               type='primary',
                               label='Logout',
                               id=self.bind(self.do_logout))
        widgets.submit_button(panel,
                               type='secondary',
                               label='Cancel',
                               id=self.bind(self.do_cancel))

    def do_logout(self):
        ok = self.model.logout()
        vs = getmeta(ok).view_screen
        self.replace(vs(self.context, ok, self.priv))

    def do_cancel(self):
        self.replace(External(self.context, self.model.url))




class LogoutOkScreen(view.Dialog):
    heartbeat = False
    def build_dialog(self, sheet):
        widgets.screen_widget(sheet, template_file=self.template_file)




class ChpasswdForm(view.FormDialog):
    screen_title = 'Change password'
    path = 'system/chpasswd'
    error = None
    bind_update = None

    def build_dialog(self, sheet):
        msg = '''
        The password is case-sensitive, and may
        include letters, digits, spaces, and
        punctuation.
        '''
        widgets.screen_title(sheet, title='Change password')
        widgets.textblock(sheet, msg)
        widgets.textblock(sheet, 'It must be at least 8 characters.')
        widgets.model_form(sheet, groups=self.get_form().groups)


    def build_dialog_buttons(self, panel):
        widgets.submit_button(panel,
                               type='primary',
                               label='Change password',
                               id=self.bind(self.do_chpasswd))
        widgets.submit_button(panel,
                               type='secondary',
                               label='Cancel',
                               id=self.bind(self.do_cancel))
        widgets.message_button(panel, text=self.error)
        self.error = None

    def do_chpasswd(self):
        self.update()
        if self.has_error:
            return
        from fenton.ext import directory
        from fenton import logging
        try:
            self.model.chpasswd()
        except security.PoorPassword, e:
            logging.log.error('chpasswd: %s', e)
            self.error = e.args and e.args[0] or 'New password is too simple'
        except (security.LoginFailed, directory.AccountLocked), e:
            self.error = 'Authentication failed'
            logging.log.error('chpasswd: %s', e)
        except Exception, e:
            logging.log.exception()
            self.error = 'Server error: please contact administrator'
            logging.log.error('chpasswd: server error: %s', e)
        else:
            logging.log.info('chpasswd: changed password for user %s', self.model.username)
            ok = self.model.get_ok()
            vs = getmeta(ok).view_screen
            self.replace(vs(self.context, ok, self.priv, self))

    def do_cancel(self):
        self.replace(External(self.context, self.model.url))




class ChpasswdOkScreen(view.Dialog):
    screen_title = 'Password changed'

    def build_dialog(self, panel):
        msg = '''
        Your password has been changed
        '''
        widgets.textblock(panel, msg)

    def build_dialog_buttons(self, panel):
        widgets.submit_button(panel,
                               type='primary',
                               label='Continue',
                               id=self.bind(self.do_continue))

    def do_continue(self):
        self.replace(External(self.context, self.model.url))




class SwitchUserForm(view.FormDialog):
    screen_title = 'Switch user'
    bind_update = None
    error = None

    def build_dialog_buttons(self, panel):
        widgets.message_button(panel, text=self.error)
        widgets.submit_button(panel,
                               type='primary',
                               label='Switch user',
                               id=self.bind(self.do_switch))
        widgets.submit_button(panel,
                               type='secondary',
                               label='Cancel',
                               id=self.bind(self.do_cancel))
        self.error = None

    def do_switch(self):
        self.update()
        if self.has_error:
            return

        ok = self.model.switch_user()
        if ok:
            vs = getmeta(ok).view_screen
            self.replace(vs(self.context, ok, self.priv, None))
        else:
            self.error = 'Failed'

    def do_cancel(self):
        self.replace(External(self.context, self.model.url))




class Login(data.Transient):
    __classid__ = None
    __permissions__ = public()
    __fields__ = ('username', 'password')
    __view__ = LoginScreen

    url = types.String()
    username = types.String(widget_config={'keypress_triggers': False})
    password = types.MaskedString(widget_config={'keypress_triggers': False})

    def authenticate(self):
        try:
            self._cx.authenticate(self.username, self.password.encode('utf-8'))
        except security.LoginFailed, e:
            return None
        return self.get_ok()

    def get_ok(self):
        raise NotImplementedError
        return self._cx.get(LoginOk, url=self.url)

    def get_help(self):
        return self._cx.get(LoginHelp, url=self.url)

    def after_load_event(self):
        rq = self._rq
        if rq.vars.url:
            self.url = rq.vars.url
        else:
            ref = rq.referer
            ref = ref and rq.host in ref and '/_' not in ref and ref
            self.url = ref or self._cx.app.home_url or '/'
        try:
            self._cx.user.verify_authenticated(self._cx)
        except security.NotAuthenticated:
            self._cx.unauthenticate()
        else:
            rq.redirect(self.url)




class LoginOk(data.Transient):
    __classid__ = None
    __permissions__ = perms(view=security.AUTHENTICATED)
    __view__ = LoginOkScreen
    url = None
    def __str__(self):
        return 'Login OK'

    def allow_chpasswd(self):
        return False

    def allow_switch(self):
        return False

    def show_landing(self):
        return False

    def get_switcher(self):
        raise NotImplementedError
        return self._cx.get(SwitchUser, url=self.url)




class LoginHelp(data.Transient):
    __classid__ = None
    __permissions__ = public()
    __view__ = LoginHelpScreen
    url = types.String()

    def allow_chpasswd(self):
        return




class Logout(data.Transient):
    __classid__ = None
    __view__ = LogoutScreen
    __permissions__ = public()

    def after_load_event(self):
        try:
            self._rq.user.verify_authenticated(self._cx)
        except security.NotAuthenticated:
            self._rq.redirect(self._cx.app.home_url)
        else:
            self.url = self._rq.referer or self._rq.app.home_url

    def logout(self):
        self._cx.unauthenticate()
        return self.get_ok()

    def get_ok(self):
        return self._cx.get(LogoutOk)



class LogoutOk(data.Transient):
    __classid__ = None
    __permissions__ = public()




class ChpasswdOk(data.Transient):
    __classid__ = None
    __permissions__ = public()
    __view__ = ChpasswdOkScreen
    url = types.String()




class Chpasswd(data.Transient):
    __classid__ = None
    __view__ = ChpasswdForm
    __permissions__ = public()

    __fields__ = (
        'username',
        'pw0',
        'pw1',
        'pw2',
    )

    url = types.String()
    username = types.String()
    pw0 = types.MaskedString(label='Current/old password')
    pw1 = types.MaskedString(label='New password')
    pw2 = types.MaskedString(label='Confirm new password')


    def chpasswd(self):
        ldap_user = self._cx.app.auth.get_user(self.username)
        if ldap_user is None:
            raise security.NoUser(self.username)

        pw0, pw1, pw2 = self.pw0, self.pw1, self.pw2

        if not security.streq(pw1, pw2):
            raise security.PoorPassword('Passwords do not match')

        if len(pw1) < 8:
            raise security.PoorPassword('New password is too short')

        ldap_user.change_password(pw0, pw1)
        from fenton.ext import crowd
        crowd.get_synchronized_user(self._cx, ldap_user, pw1)

    def get_ok(self):
        return self._cx.get(ChpasswdOk, url=self.url)



class SwitchUser(data.Transient):
    __classid__ = None
    __fields__ = ('username',)
    __permissions__ = perms(view=security.RESTRICTED)
    __view__ = SwitchUserForm

    username = types.String()
    url = types.String()

    def switch_user(self):
        if self._cx.switch_user(self.username):
            return self.get_ok()

    def get_ok(self):
        raise NotImplementedError
        return self._cx.get(LoginOk, url=self.url)



class Ping(data.Transient):
    __permissions__ = public()
    __view__ = view.Plain
    content_type = 'text/plain'
    def __str__(self):
        return 'OK'




